package world

import (
	"MetroBuilderGameJam/gameEngine/geom"
)

func NewBranchEdge(station *Station, branch *BranchCompleted) *BranchEdge {
	if station == nil || branch == nil {
		return nil
	}
	b := &BranchEdge{
		station: station,
		branch:  branch,
	}

	err := station.TryConnectToBranch(b)
	if err != nil {
		_ = station.TryConnectToBranch(b)
		return nil
	}

	return b

}

type BranchEdge struct {
	station    *Station
	branch     *BranchCompleted
	angleShift geom.Radian
}

func (b *BranchEdge) From() *geom.Point {
	return b.station.Position()
}

func (b *BranchEdge) To() *geom.Point {
	return b.calcPointTo()
}

func (b *BranchEdge) Inverse() {
	return
}

func (b *BranchEdge) IsInversed() bool {
	return b.station == b.branch.from && b.branch.IsInversed()
}

func (b *BranchEdge) FromStation() *Station {
	return b.station
}

func (b *BranchEdge) RailRoad() *RailRoad {
	return b.branch.RailRoad()
}

func (b *BranchEdge) GetBaseBranch() *BranchCompleted {
	return b.branch
}

func (b *BranchEdge) ToStation() *Station {
	return nil
}

func (b *BranchEdge) CalcBendPosition() geom.Point {
	return *b.From()
}

func (b *BranchEdge) calcPointTo() *geom.Point {
	if b.branch == nil {
		return nil
	}
	side, index := b.station.GetSlotForBranch(b)
	var dir geom.Vec2
	if index >= 0 {
		dir = geom.Degrees(side).ToRadians().ToVec()
	} else {
		from := *b.branch.From()
		to := *b.branch.To()
		if b.branch.IsInversed() {
			from = *b.branch.To()
			to = *b.branch.From()
		}
		bendPosition := geom.CalcBendPosition(from, to)
		dir = (b.From().AsVec2().Sub(bendPosition.AsVec2()).ToRadian() + b.angleShift).ToVec().Normalize()
	}
	p := b.From().AsVec2().Add(dir).AsPoint()
	return &p
}

func NewBranchProject(from *Station, railRoad *RailRoad) *BranchProject {
	b := &BranchProject{
		weight: 1,
		branchBase: branchBase{
			from: from,
			road: railRoad,
		},
	}

	err := from.TryConnectToBranch(b)
	if err != nil {
		b.Inverse()
		b.WrongBuilding = from.TryConnectToBranch(b)
	}

	return b
}

type BranchProject struct {
	branchBase
	to            *geom.Point
	ChangedBranch *BranchCompleted
	WrongBuilding *BranchErrorAt
	weight        int
}

func (b *BranchProject) SetPoint(to *geom.Point) {
	b.to = to
	b.WrongBuilding = b.from.TryConnectToBranch(b)
}
func (b *BranchProject) To() *geom.Point {
	if b.to == nil {
		return b.from.Position()
	}
	return b.to
}

func (b *BranchProject) SetFromStation(station *Station) {
	b.from = station
}

func (b *BranchProject) IsChangeFor(branch *BranchCompleted) bool {
	return b.ChangedBranch == branch
}

func (b *BranchProject) Inverse() {
	b.branchBase.Inverse()
	if b.from.TryConnectToBranch(b) != nil {
		b.branchBase.Inverse()
	}
}

func (b *BranchProject) CalcBendPosition() geom.Point {
	from := *b.From()
	if b.to == nil {
		return from
	}
	to := *b.to

	if b.IsInversed() {
		from, to = to, from
	}

	return geom.CalcBendPosition(from, to)
}

func (b *BranchProject) canLoopRoadIn(station *Station) bool {
	return b.road.IsEdgedStation(station) && b.from != station
}

func NewCompletedBranch(from, to *Station, road *RailRoad) (*BranchCompleted, error) {
	if from == nil || to == nil {
		return nil, nil
	}
	b := &BranchCompleted{
		branchBase: branchBase{
			from: from,
			road: road,
		},
		to: to,
	}

	err := from.TryConnectToBranch(b)
	if err != nil {
		b.Inverse()
		err = from.TryConnectToBranch(b)
		if err != nil {
			return nil, err
		}

		err = to.TryConnectToBranch(b)
		if err != nil {
			return nil, err
		}
		return b, nil
	}

	err = to.TryConnectToBranch(b)
	if err != nil {
		b.Inverse()
		err = to.TryConnectToBranch(b)
		if err != nil {
			return nil, err
		}

		err = from.TryConnectToBranch(b)
		if err != nil {
			return nil, err
		}
	}

	return b, nil
}

func CompleteBranchProject(b *BranchProject, to *Station) *BranchCompleted {
	if b == nil || to == nil {
		return nil
	}
	defer func() {
		b.weight = 1
	}()

	b.weight = 0
	newB, err := NewCompletedBranch(b.from, to, b.road)
	if berr, ok := err.(*BranchErrorAt); ok {
		b.WrongBuilding = berr
	}
	if err != nil || newB == nil {
		return nil
	}

	if b.IsInversed() {
		newB.Inverse()
	}

	return newB
}

type BranchCompleted struct {
	branchBase
	to *Station
}

func (b *BranchCompleted) To() *geom.Point {
	if b.to == nil {
		return nil
	}
	return b.to.Position()
}

func (b *BranchCompleted) AttachToRoad(railRoad *RailRoad) {
	b.road = railRoad
}

func (b *BranchCompleted) reverse() *BranchCompleted {
	b.from, b.to = b.to, b.from
	b.Inverse()
	return b
}

func (b *BranchCompleted) Inverse() {
	b.branchBase.Inverse()
	if b.from.TryConnectToBranch(b) != nil {
		b.branchBase.Inverse()
	}
	if b.to.TryConnectToBranch(b) != nil {
		b.branchBase.Inverse()
		_ = b.from.TryConnectToBranch(b)
	}
}

func (b *BranchCompleted) ToStation() *Station {
	return b.to
}

func (b *BranchCompleted) CalcBendPosition() geom.Point {
	from := *b.From()
	to := from
	if b.To() != nil {
		to = *b.To()
	}

	if b.IsInversed() {
		from, to = to, from
	}
	return geom.CalcBendPosition(from, to)
}

func (b *BranchCompleted) HasStation(station *Station) bool {
	return b.from == station || b.to == station
}

func (b *BranchCompleted) IsSameWith(branch *BranchCompleted) bool {
	return branch != nil &&
		b.IsInversed() == branch.IsInversed() &&
		b.From() == branch.From() &&
		b.To() == branch.To()

}

func (b *BranchCompleted) GetAnotherStation(s *Station) *Station {
	if b.FromStation() == s {
		return b.ToStation()
	} else {
		return b.FromStation()
	}
}

func NewRememberedBranch(completed *BranchCompleted) *BranchRemembered {
	return &BranchRemembered{
		*completed,
		[]*BranchRemembered{},
	}
}

type BranchRemembered struct {
	BranchCompleted
	Additional []*BranchRemembered
}

func (b *BranchRemembered) AddBranch(branch *BranchCompleted) *BranchRemembered {
	br := NewRememberedBranch(branch)
	b.Additional = append(b.Additional, br)
	return br
}

func (b *BranchRemembered) AddNextBranch(branch *BranchRemembered) {
	b.Additional = append(b.Additional, branch)
}

func (b *BranchRemembered) HasPathToRoad() bool {
	if b.HasConnectionToRoad() {
		return true
	}
	for _, branch := range b.Additional {
		if branch.HasConnectionToRoad() {
			return true
		}
	}
	return false
}

func (b *BranchRemembered) HasConnectionToRoad() bool {
	return b.road.HasStation(b.from) || b.road.HasStation(b.to)
}

func (b *BranchRemembered) HasBranch(branch *BranchCompleted) bool {
	if b.IsSameWith(branch) {
		return true
	}
	for _, br := range b.Additional {
		if br.IsSameWith(branch) {
			return true
		}
	}
	return false
}

type branchBase struct {
	from     *Station
	road     *RailRoad
	inversed bool
}

func (b *branchBase) Inverse() {
	b.inversed = !b.inversed
}

func (b *branchBase) IsInversed() bool {
	return b.inversed
}

func (b *branchBase) From() *geom.Point {
	if b.from == nil {
		return nil
	}
	return b.from.Position()
}

func (b *branchBase) FromStation() *Station {
	return b.from
}

func (b *branchBase) RailRoad() *RailRoad {
	return b.road
}

func (b *branchBase) ToStation() *Station {
	return nil
}
